#include "Parser.h"

#include <cstdlib>    // has isdigit(), strtol(), strtod()
//#include <stdlib.h>
//#include <stdexcept>  // exceptions thrown by stol and stod
#include <iostream>
#include <string>
#include "Tools.h"    // has iequal()
#include "Log.h"

Parser::Parser(const std::string& p_file_name) {
    open_file(p_file_name);
    init();
}

Parser::Parser(std::istream& stream) {
    set_stream(stream);
    init();
}

void Parser::open_file(const std::string& p_file_name) {
    file_name = p_file_name;
    line_number = 0;

    static std::ifstream ifs;
    ifs.open(p_file_name);
    if (!ifs || ifs.bad())
        throw std::string("Parser can't open file!\n");
    input = &ifs;
}

void Parser::set_stream(std::istream& stream) {
    file_name = "(input stream)";
    line_number = 0;

    input = &stream;
}

Parser::~Parser() {
    delete input_line;
}

void Parser::init() {
    buffer_size = 65000;
    input_line = new char[buffer_size];
    set_charset(" \t","[]<>{}");
    read_line(nxt_line);
}

void Parser::next_line() {
    if (input->eof())
        throw std::string("Parser: premature eof\n");
    cur_line = nxt_line;
    read_line(nxt_line);

    tokenize();
    line_number++;
}

void Parser::read_line(std::string& line) {
    input_line[0] = '\0';
    input->getline(input_line, buffer_size);
    line = input_line;
}

void Parser::tokenize() {
    tokens.clear();

    bool in_token = false;
    int token_start;
    int token_length = 0; // 0 means no active token
    for (int i = 0; i < cur_line.length(); i++) {
        if (whitespace.find(cur_line[i]) != std::string::npos) {
            if (token_length > 0) {
                // active token just ended
                tokens.push_back(cur_line.substr(token_start, token_length));
                token_length = 0;
            }
        }
        else if (single_char_tokens.find(cur_line[i]) != std::string::npos) {
            if (token_length > 0) {
                // active token just ended, followed by single-char token
                tokens.push_back(cur_line.substr(token_start, token_length));
                token_length = 0;
            }
            tokens.push_back(cur_line.substr(i, 1));
        }
        else {
            // it's part of a token
            if (token_length == 0) {
                // start a new one.
                token_start = i;
            }
            token_length++;
        }
    }

    if (token_length > 0) {
        // line ended at end of last token.
        tokens.push_back(cur_line.substr(token_start, token_length));
        token_length = 0;
    }

    // Prepare for iteration through tokens
    n_tokens = tokens.size();
    i_token = 0;
}

bool Parser::eof() {
    return input->eof();
}

bool Parser::matches(const std::string& s, int initial_state) {
    // Finite state machines for matching integers and floats.
    // The five fields in each state are:
    // - next state if '-'
    // - next state if '.'
    // - next state if digit
    // - next state if any other char
    // - this state is/isn't accepting
    // For ints, start at state 1
    // For floats, start at state 4
    // state 0 is filler
    // state 8 is a dead-end non-matching state

    // This is obsolete (std::stol and std::stod do the same thing),
    // but I kept it for sentimental reasons.
    static int next[9][5] = {{8,8,8,8,0},
                             {3,8,2,8,0},
                             {8,8,2,8,1},
                             {8,8,2,8,0},
                             {5,7,6,8,0},
                             {8,7,6,8,0},
                             {8,7,6,8,1},
                             {8,8,7,8,1},
                             {8,8,8,8,0}};

    int state = initial_state;
    for (int i=0; i<s.length(); i++) {
        char c = s[i];
        if      (c == '-')
            state = next[state][0];
        else if (c == '.')
            state = next[state][1];
        else if (std::isdigit(c))
            state = next[state][2];
        else
            state = next[state][3];
    }
    return next[state][4] == 1;
}

int Parser::match(int& value) {
    return match_int(value);
}

int Parser::match(double &value) {
    return match_double(value);
}

int Parser::match(std::string& value) {
    return match_any_word(value);
}

int Parser::match_int(int& value) {
    if (line_done()) {
        return 0;
    }

    char *str_end;
    const char *token = tokens[i_token++].c_str();
    long i = std::strtol(token, &str_end, 10);
    if (str_end == token) {
        return 0;
    }
    else {
        value = (int) i;
        return 1;
    }

    // try {
    //     value = std::stol(sfp.get_token(i_token++));
    // }
    // catch (std::invalid_argument e) {
    //     return 0;
    // }
    // catch (std::out_of_range e) {
    //     return 0;
    // }
    // return 1;

/*

    std::string token = sfp.get_token(i_token++);
    if (matches(token, 1)) {
        value = atoi(token);
        return 1;
    }
    return 0;
*/
}

int Parser::match_double(double& value) {
    if (line_done())
        return 0;

    char *str_end;
    const char *token = tokens[i_token++].c_str();
    value = std::strtol(token, &str_end, 10);
    if (str_end == token) {
        return 0;
    }
    else {
        return 1;
    }

    // try {
    //     value = std::stod(sfp.get_token(i_token++));
    // }
    // catch (std::invalid_argument e) {
    //     return 0;
    // }
    // catch (std::out_of_range e) {
    //     return 0;
    // }
    // return 1;

/*
    std::string token = sfp.get_token(i_token++);
    if (matches(token, 4)) {
        value = atof(token);
        return 1;
    }
    return 0;
*/
}

int Parser::match_char(const std::string& char_set) {
    if (line_done())
        return 0;

    std::string token = tokens[i_token++];
    size_t location = char_set.find(token[0]);
    if (char_set.find(token[0]) != std::string::npos) {
        return 1;
    }

    return 0;
}

int Parser::match_word(const std::string& pattern, bool ignore_case) {
    if (line_done())
        return 0;

    std::string token = tokens[i_token++];
    if ( ignore_case
         ?
         iequals(pattern, token)
         :
         pattern == token)
        return 1;
    return 0;
}

int Parser::match_any_word(std::string& word) {
    if (line_done())
        return 0;
    word = tokens[i_token++];
    return 1;
}

void Parser::backup(int count) {
    i_token -= count;
    if (i_token < 0)
        i_token = 0;
}

bool Parser::line_done() {
    return i_token >= n_tokens;
}

void Parser::set_charset(const std::string& ws,
                         const std::string& sc) {
    whitespace = ws;
    single_char_tokens = sc;
}
